package com.wcity.manage.dao.dal.plugins;

import com.wcity.manage.dao.dal.model.PageModel;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

/**
 * Mybatis分页插件
 * 
 * @author dy
 *
 */
@Intercepts({
	@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})
	})
public class PagePlugins implements Interceptor {
	
	private static final Logger log = LoggerFactory.getLogger(PagePlugins.class); 
	
	/**
     * 拦截器
     * 
     * @return
     */
	public Object intercept( Invocation invocation ) throws Throwable { 
    	   
        // 拦截StatementHandler的prepare方法再组装sql语句
        if ( invocation.getTarget() instanceof StatementHandler ) {
        	
        	// 1.1~获取StatementHandler对象代理
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();       
            MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);  
            // 1.2.1~分离代理对象链
            while ( metaStatementHandler.hasGetter("h") ) { 
            	
                Object object = metaStatementHandler.getValue("h");  
                metaStatementHandler = SystemMetaObject.forObject(object);  
            }
            // 1.2.2~分离最后一个代理对象的目标类
            while (metaStatementHandler.hasGetter("target")) { 
            	
                Object object = metaStatementHandler.getValue("target");  
                metaStatementHandler = SystemMetaObject.forObject(object);  
            } 
            
            // 1.3~获取BoundSql
            MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");  
            BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql"); 
            
            // 1.4~获取BoundSql绑定的对象
            Object paramObj = boundSql.getParameterObject();
            String sql = boundSql.getSql();
            
            // 1.5~判断Mapper映射传入参数是否为PageModel
            if ( paramObj instanceof PageModel) {
            	
            	PageModel<?> pageModel = (PageModel<?>) paramObj;  
            	
            	// 组装分页sql语句
            	log.debug( "[分页插件] ==>       Getting Sql: {}", sql.replaceAll("\\s+", " ") );
           
                String pageSql = getPageSql( sql, pageModel );  
                
                log.debug( "[分页插件] ==>  Setting Page Sql: {}", pageSql.replaceAll("\\s+", " ") );
                   
                metaStatementHandler.setValue( "delegate.boundSql.sql",  pageSql );
                
                // 1.6~设置分页总数
                Connection connection = (Connection) invocation.getArgs()[0];      
                setPageTotal( sql, connection, mappedStatement, boundSql, pageModel );  
                
                return invocation.proceed(); 
                
            } else {
            	
            	return invocation.proceed(); 	
            } 
        } 
        return null;  
    }  
	
    /**
     * 组装分页sql语句(MySql)
     * 
     * @param sql
     * @param pageModel
     * @return
     */
	private String getPageSql( String sql, PageModel<?> pageModel ) {
    	
        sql += " LIMIT " + pageModel.getPageStart() + " , " + pageModel.getNumPerPage();
        return sql;  
    }  
  
    /**
     * 设置分页总数(MySql)
     * 
     * @param sql
     * @param connection
     * @param mappedStatement
     * @param boundSql
     * @param pageModel
     */
	private void setPageTotal( String sql, Connection connection, 
			MappedStatement mappedStatement, BoundSql boundSql, PageModel<?> pageModel ) { 
		
		log.debug( "[分页插件] ==>       Getting Sql: {}", sql.replaceAll("\\s+", " ") );
		
		// 组装总数查询sql语句 
		String vSql = ""; 	
		if ( sql.indexOf("FROM") > 0 ) {
			
			vSql = sql.substring( sql.indexOf("FROM") + 4 );
			
		} else if ( sql.indexOf("from") > 0 ) {
			
			vSql = sql.substring( sql.indexOf("from") + 4 );
		}  
        String countSql = "SELECT COUNT(1) FROM " + vSql; 
        
        log.debug( "[分页插件] ==> Setting Total Sql: {}", countSql.replaceAll("\\s+", " ") );
        
        PreparedStatement countStmt = null;  
        ResultSet rs = null;  
        
        try {  
        	
            countStmt = connection.prepareStatement(countSql);  
            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,  
                    boundSql.getParameterMappings(), boundSql.getParameterObject()); 
            
            ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBS);  
            parameterHandler.setParameters(countStmt); 
            
            rs = countStmt.executeQuery();  
            int totalCount = 0;  
            if ( rs.next() ) {  
            	
                totalCount = rs.getInt(1);  
            }  
            
            log.debug( "[分页插件] <==         Get Total: {}", totalCount );
            pageModel.setTotal(totalCount);  
               
        } catch ( SQLException e ) {  
        	
            log.error("SQLException:{}", e);    
        } finally {  
        	
            try {   
            	
                rs.close();    
                countStmt.close();       
            } catch ( SQLException e ) { 
            	
                log.error("SQLException:{}", e);  
            }        
        }  
    } 
    
    /**
     * 封装原始对象
     * 
     */
    public Object plugin( Object target ) {  
    	
        if (target instanceof StatementHandler || target instanceof ResultSetHandler) {  
        	
            return Plugin.wrap(target, this);  
        } else {  
            return target;  
        }  
    }  
    
    /**
     * 注册拦截器时调用设置属性
     * 
     */
    public void setProperties( Properties properties ) {}  
       
}
